using System;
using System.Collections;
using System.Text;
using System.Web;
using System.Web.Security;

using Azke.Core.Domain;
using Azke.Core.Service;
using Azke.Core.Util;

namespace Azke.Core.Security
{
    public class NHRoleProvider : RoleProvider
    {
        
        #region Private Members

        private LogManager _logger = LogManager.GetLogger();
        private CoreRepository _coreRepository;
        private string _applicationName;

        #endregion

        #region Constructor
        //
        // Summary:
        //     Creates an instance of the System.Web.Security.NHRoleProvider class.
        public NHRoleProvider()
        {
            
        }
        #endregion

        #region Initializor

        //
        // Summary:
        //     Initializes the SQL Server role provider with the property values specified
        //     in the ASP.NET application's configuration file. This method is not intended
        //     to be used directly from your code.
        //
        // Parameters:
        //   config:
        //     A System.Collections.Specialized.NameValueCollection that contains the names
        //     and values of configuration options for the role provider.
        //
        //   name:
        //     The name of the System.Web.Security.NHRoleProvider instance to initialize.
        //
        // Exceptions:
        //   System.Configuration.Provider.ProviderException:
        //     The connectionStringName attribute is empty or does not exist in the application
        //     configuration file for this System.Web.Security.NHRoleProvider instance.-or-The
        //     applicationName attribute exceeds 256 characters.-or-The application configuration
        //     file for this System.Web.Security.NHRoleProvider instance contains an unrecognized
        //     attribute.
        //
        //   System.ArgumentNullException:
        //     config is null.
        //
        //   System.Web.HttpException:
        //     The ASP.NET application is not running at System.Web.AspNetHostingPermissionLevel.Medium
        //     trust or higher.
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            //
            // Initialize values from web.config.
            //

            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                name = "NHRoleProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "NHibernate Role provider");
            }

            // Initialize the abstract base class.
            base.Initialize(name, config);

            if (config["applicationName"] == null || config["applicationName"].Trim() == "")
            {
                _applicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
            }
            else
            {
                _applicationName = config["applicationName"];
            }

            // NHibernate Session
            try
            {
                this._coreRepository = (CoreRepository)HttpContext.Current.Items["CoreRepository"];
                this._logger.Write(LogLevel.DEBUG, this.ToString(), "CoreRepository from HttpContext used.");
            }
            catch
            {
                this._coreRepository = new CoreRepository(true);
                this._logger.Write(LogLevel.WARN, this.ToString(), "No CoreRepository in HttpContext found, creating new CoreRepository.");
            }
        }

        #endregion

        #region Properties
        //
        // Summary:
        //     Gets or sets the name of the application for which to store and retrieve
        //     role information.
        //
        // Returns:
        //     The name of the application for which to store and retrieve role information.
        //     The default is the System.Web.HttpRequest.ApplicationPath property value
        //     for the current System.Web.HttpContext.Request.
        //
        // Exceptions:
        //   System.Configuration.Provider.ProviderException:
        //     An attempt was made to set the System.Web.Security.NHRoleProvider.ApplicationName
        //     to a string that is longer than 256 characters.
        //
        //   System.Web.HttpException:
        //     An attempt was made to set the System.Web.Security.NHRoleProvider.ApplicationName
        //     property by a caller that does not have System.Web.AspNetHostingPermissionLevel.High
        //     ASP.NET hosting permission.
        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }
        #endregion

        #region Methods
        //
        // Summary:
        //     Adds the specified user names to each of the specified roles.
        //
        // Parameters:
        //   roleNames:
        //     A string array of role names to add the specified user names to.
        //
        //   usernames:
        //     A string array of user names to be added to the specified roles.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     One of the roles in roleNames is an empty string or contains a comma.-or-One
        //     of the users in usernames is an empty string or contains a comma.-or-One
        //     of the roles in roleNames is longer than 256 characters.-or-One of the users
        //     in usernames is longer than 256 characters.-or-roleNames contains a duplicate
        //     element.-or-usernames contains a duplicate element.
        //
        //   System.ArgumentNullException:
        //     One of the roles in roleNames is null.-or-One of the users in usernames is
        //     null.
        //
        //   System.Configuration.Provider.ProviderException:
        //     One or more of the specified role names was not found.-or- One or more of
        //     the specified user names was not found.-or- One or more of the specified
        //     user names is already associated with one or more of the specified role names.-or-
        //     An unknown error occurred while communicating with the database.
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            foreach (string rolename in roleNames)
            {
                if (!RoleExists(rolename))
                {
                    throw new System.Configuration.Provider.ProviderException("Role name not found.");
                }
            }

            foreach (string username in usernames)
            {
                if (username.IndexOf(',') > 0)
                {
                    throw new ArgumentException("User names cannot contain commas.");
                }

                foreach (string rolename in roleNames)
                {
                    if (IsUserInRole(username, rolename))
                    {
                        throw new System.Configuration.Provider.ProviderException("User is already in role.");
                    }
                }
            }

            try
            {            
                foreach (string username in usernames)
                {
                    User user = this._coreRepository.GetUserByUsername(username);
                    foreach (string rolename in roleNames)
                    {
                        Role role = this._coreRepository.GetRoleByRoleName(rolename);
                        user.Roles.Add(role);
                    }
                    this._coreRepository.UpdateObject(user);
                }
            }
            catch (Exception ex)
            {
                _logger.Write(LogLevel.ERROR, this.ToString(), "An error occured while trying to add a user to a role.", ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to add a user to a role.");
            }
        }

        //
        // Summary:
        //     Adds a new role to the role database.
        //
        // Parameters:
        //   roleName:
        //     The name of the role to create.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     roleName is an empty string or contains a comma.-or-roleName is longer than
        //     256 characters.
        //
        //   System.Configuration.Provider.ProviderException:
        //     roleName already exists in the database.-or- An unknown error occurred while
        //     communicating with the database.
        //
        //   System.ArgumentNullException:
        //     roleName is null.
        public override void CreateRole(string roleName)
        {
            if (roleName.IndexOf(',') > 0 || roleName.Length > 256)
            {
                throw new ArgumentException("Role names cannot contain commas or be longer than 255.");
            }

            if (String.IsNullOrEmpty(roleName))
            {
                throw new ArgumentException("Role names cannot be null or empty.");
            }

            if (RoleExists(roleName))
            {
                throw new System.Configuration.Provider.ProviderException("Role name already exists.");
            }

            try
            {
                Role newRole = new Role();
                newRole.ApplicationName = this._applicationName;
                newRole.RoleName = roleName;
                newRole.UpdateTimestamp = DateTime.Now;
                newRole.Description = "";
                this._coreRepository.SaveObject(newRole);           
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), "An error occured while trying to add a new role.", ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to add a new role.");
            }
        }

        //
        // Summary:
        //     Removes a role from the role database.
        //
        // Parameters:
        //   throwOnPopulatedRole:
        //     If true, throws an exception if roleName has one or more members.
        //
        //   roleName:
        //     The name of the role to delete.
        //
        // Returns:
        //     true if the role was successfully deleted; otherwise, false.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     roleName is null (Nothing in Visual Basic).
        //
        //   System.ArgumentException:
        //     roleName is an empty string or contains a comma.-or-roleName is longer than
        //     256 characters.
        //
        //   System.Configuration.Provider.ProviderException:
        //     roleName has one or more members and throwOnPopulatedRole is true.-or- An
        //     unknown error occurred while communicating with the database.
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            if (!RoleExists(roleName))
            {
                throw new System.Configuration.Provider.ProviderException("Role does not exist.");
            }

            if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0)
            {
                throw new System.Configuration.Provider.ProviderException("Cannot delete a populated role.");
            }

            try
            {
                Role roleToDelete = this._coreRepository.GetRoleByRoleName(roleName);

                // TODO:
                // Must check if role are used in Sections or Nodes before delete?
                

                if (roleToDelete != null)
                {
                    IList usersInRole = roleToDelete.Users;
                    if (usersInRole != null && usersInRole.Count > 0)
                    {
                        foreach (User userInRole in usersInRole)
                        {
                            userInRole.Roles.Remove(roleToDelete);
                            this._coreRepository.UpdateObject(userInRole);
                        }
                    }
                    this._coreRepository.DeleteObject(roleToDelete);
                }
                else
                {
                    this._logger.Write(LogLevel.WARN, this.ToString(), String.Format("Invalid rolename, not found: {0}.", roleName));
                    return false;
                }

                return true;
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), String.Format("An error occured while trying to delete role {0}.", roleName), ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to delete a role.");
            }
        }

        //
        // Summary:
        //     Gets an array of user names in a role where the user name contains the specified
        //     user name to match.
        //
        // Parameters:
        //   usernameToMatch:
        //     The user name to search for.
        //
        //   roleName:
        //     The role to search in.
        //
        // Returns:
        //     A string array containing the names of all the users where the user name
        //     matches usernameToMatch and the user is a member of the specified role.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     roleName is null (Nothing in Visual Basic).-or-usernameToMatch is null.
        //
        //   System.ArgumentException:
        //     roleName is an empty string or contains a comma.-or-usernameToMatch is an
        //     empty string.-or-roleName is longer than 256 characters.-or-usernameToMatch
        //     is longer than 256 characters.
        //
        //   System.Configuration.Provider.ProviderException:
        //     roleName was not found in the database.-or- An unknown error occurred while
        //     communicating with the database.
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            if (!RoleExists(roleName))
            {
                throw new System.Configuration.Provider.ProviderException("Role does not exist.");
            }
            if (String.IsNullOrEmpty(roleName))
            {
                throw new ArgumentException("Rolename is null or empty.");
            }

            try
            {
                string[] userNames = this._coreRepository.FindUsersInRoleByRolename(roleName, usernameToMatch);
                return userNames;
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), String.Format("An error occured while trying to find users in role {0}.", roleName), ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to find users in specified role.");
            }
        }
        //
        // Summary:
        //     Gets a list of all the roles for the application.
        //
        // Returns:
        //     A string array containing the names of all the roles stored in the database
        //     for a particular application.
        //
        // Exceptions:
        //   System.Configuration.Provider.ProviderException:
        //     An unknown error occurred while communicating with the database.
        public override string[] GetAllRoles()
        {
            try
            {
                IList roles = this._coreRepository.GetAll(typeof(Role), null);
                int numberOfRoles = roles.Count;
                if (roles != null && numberOfRoles > 0)
                {
                    string[] roleNames = new string[numberOfRoles];
                    int i = 0;
                    foreach (Role role in roles)
                    {
                        roleNames[i] = role.RoleName;
                        i++;
                    }
                    return roleNames;
                }
                return new string[0];
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), "An error occured while trying to get all roles in database.", ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to get all roles in database: " + ex.Message);
            }
        }
        
        //
        // Summary:
        //     Gets a list of the roles that a user is in.
        //
        // Parameters:
        //   username:
        //     The user to return a list of roles for.
        //
        // Returns:
        //     A string array containing the names of all the roles that the specified user
        //     is in.
        //
        // Exceptions:
        //   System.Configuration.Provider.ProviderException:
        //     An unknown error occurred while communicating with the database.
        //
        //   System.ArgumentException:
        //     username contains a comma.-or-username is longer than 256 characters.
        //
        //   System.ArgumentNullException:
        //     username is null.
        public override string[] GetRolesForUser(string username)
        {
            if (String.IsNullOrEmpty(username))
            {
                throw new ArgumentException("Username is null or empty.");
            }

            if (username.IndexOf(',') > 0)
            {
                throw new ArgumentException("User names cannot contain commas.");
            }

            try
            {
                User user = this._coreRepository.GetUserByUsername(username);
                IList userRoles = user.Roles;
                string[] roleNames = new string[userRoles.Count];
                if (userRoles != null && userRoles.Count > 0)
                {
                    int i = 0;
                    foreach (Role userRole in userRoles)
                    {
                        roleNames[i] = userRole.RoleName;
                        i++;
                    }
                }
                return roleNames;
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), String.Format("An error occured while trying to get all roles for user {0}.", username), ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to get all roles for a user");
            }
        }
        
        //
        // Summary:
        //     Gets a list of users in the specified role.
        //
        // Parameters:
        //   roleName:
        //     The name of the role to get the list of users for.
        //
        // Returns:
        //     A string array containing the names of all the users who are members of the
        //     specified role.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     roleName is an empty string or contains a comma.-or-roleName is longer than
        //     256 characters.
        //
        //   System.Configuration.Provider.ProviderException:
        //     rolename was not found in the database.-or- An unknown error occurred while
        //     communicating with the database.
        //
        //   System.ArgumentNullException:
        //     roleName is null.
        public override string[] GetUsersInRole(string roleName)
        {
            if (!RoleExists(roleName))
            {
                throw new System.Configuration.Provider.ProviderException("Role does not exist.");
            }

            if (String.IsNullOrEmpty(roleName))
            {
                throw new ArgumentException("Rolename is null or empty.");
            }

            try
            {
                Role role = this._coreRepository.GetRoleByRoleName(roleName);
                IList usersInRole = role.Users;
                if (usersInRole != null && usersInRole.Count > 0)
                {
                    int i = 0;
                    string[] userNames = new string[usersInRole.Count];
                    foreach (User user in usersInRole)
                    {
                        userNames[i] = user.UserName;
                        i++;
                    }
                    return userNames;
                }                      
                return new string[0];
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), String.Format("An error occured while trying to get all users in role {0}.", roleName), ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to get all users in a role.");
            }
        }
       
        //
        // Summary:
        //     Gets a value indicating whether the specified user is in the specified role.
        //
        // Parameters:
        //   username:
        //     The user name to search for.
        //
        //   roleName:
        //     The role to search in.
        //
        // Returns:
        //     true if the specified user name is in the specified role; otherwise, false.
        //
        // Exceptions:
        //   System.Configuration.Provider.ProviderException:
        //     An unknown error occurred while communicating with the database.
        //
        //   System.ArgumentNullException:
        //     roleName is null.-or-username is null.
        //
        //   System.ArgumentException:
        //     roleName is an empty string or contains a comma.-or-username is contains
        //     a comma.-or-roleName is longer than 256 characters.-or-username is longer
        //     than 256 characters.
        public override bool IsUserInRole(string username, string roleName)
        {
            if (String.IsNullOrEmpty(roleName) || String.IsNullOrEmpty(username))
                throw new ArgumentException("Username and/or RoleName is null or empty.");

            if (!RoleExists(roleName))
            {
                throw new System.Configuration.Provider.ProviderException("Role name not found.");
            }

            if (username.IndexOf(',') > 0)
            {
                throw new ArgumentException("User names cannot contain commas.");
            }

            try
            {
                bool isInRole = false;
                User user = this._coreRepository.GetUserByUsername(username);
                IList userRoles = user.Roles;
                if (userRoles != null && userRoles.Count > 0)
                {
                    foreach (Role userRole in userRoles)
                    {
                        if (userRole.RoleName == roleName)
                            isInRole = true;
                    }
                }
                return isInRole;
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), String.Format("An error occured while trying to check if user {0} is in role.", username), ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to check if user is in role.");
            }
        }
        
        //
        // Summary:
        //     Removes the specified user names from the specified roles.
        //
        // Parameters:
        //   roleNames:
        //     A string array of role names to remove the specified user names from.
        //
        //   usernames:
        //     A string array of user names to be removed from the specified roles.
        //
        // Exceptions:
        //   System.ArgumentException:
        //     One of the roles in roleNames is an empty string or contains a comma.-or-One
        //     of the users in usernames is an empty string or contains a comma.-or-One
        //     of the roles in roleNames is longer than 256 characters.-or-One of the users
        //     in usernames is longer than 256 characters.-or-roleNames contains a duplicate
        //     element.-or-usernames contains a duplicate element.
        //
        //   System.ArgumentNullException:
        //     One of the roles in roleNames is null.-or-One of the users in usernames is
        //     null.
        //
        //   System.Configuration.Provider.ProviderException:
        //     One or more of the specified user names was not found.-or- One or more of
        //     the specified role names was not found.-or- One or more of the specified
        //     user names is not associated with one or more of the specified role names.-or-
        //     An unknown error occurred while communicating with the database.
        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            foreach (string rolename in roleNames)
            {
                if (!RoleExists(rolename))
                {
                    throw new System.Configuration.Provider.ProviderException("Role name not found.");
                }
            }

            foreach (string username in usernames)
            {
                foreach (string rolename in roleNames)
                {
                    if (!IsUserInRole(username, rolename))
                    {
                        throw new System.Configuration.Provider.ProviderException("User is not in role.");
                    }
                }
            }

            try
            {
                foreach (string username in usernames)
                {
                    User user = this._coreRepository.GetUserByUsername(username);
                    foreach (string rolename in roleNames)
                    {
                        Role role = this._coreRepository.GetRoleByRoleName(rolename);
                        user.Roles.Remove(role);
                    }
                    this._coreRepository.UpdateObject(user);
                }
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), "An error occured while trying to delete users from roles.", ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to delete users from roles.");
            }
        }
        
        //
        // Summary:
        //     Gets a value indicating whether the specified role name already exists in
        //     the role database.
        //
        // Parameters:
        //   roleName:
        //     The name of the role to search for in the database.
        //
        // Returns:
        //     true if the role name already exists in the database; otherwise, false.
        //
        // Exceptions:
        //   System.Configuration.Provider.ProviderException:
        //     An unknown error occurred while communicating with the database.
        //
        //   System.ArgumentException:
        //     roleName is an empty string or contains a comma.-or-roleName is longer than
        //     256 characters.
        //
        //   System.ArgumentNullException:
        //     roleName is null.
        public override bool RoleExists(string roleName)
        {
            if (String.IsNullOrEmpty(roleName))
            {
                throw new ArgumentException("Rolename is null or empty.");
            }

            try
            {
                Role role = this._coreRepository.GetRoleByRoleName(roleName);
                if (role != null)
                    return true;
                else
                    return false;
            }
            catch (Exception ex)
            {
                this._logger.Write(LogLevel.ERROR, this.ToString(), String.Format("An error occured while trying to check if role {0} exists.", roleName), ex);
                throw new System.Configuration.Provider.ProviderException("An error occured while trying to check if role exists.");
            }
        }

        #endregion
    }
}
